package services

import (
	"context"
	"encoding/json"
	"math/rand"
	"sanrenx.com/xyxf/app/daos"
	"sanrenx.com/xyxf/app/models"
	"sanrenx.com/xyxf/app/services/cache"
	"sanrenx.com/xyxf/pkg/gredis"
	"sanrenx.com/xyxf/pkg/logging"
	"sanrenx.com/xyxf/pkg/orm"
	"strconv"
	"strings"
	"unicode"
)

/**
 * @Description: 文章服务
 */
type SutraSrv struct{}

/**
 * @Description: 分页获取分类下藏经列表
 * @receiver srv
 * @param offset 起始
 * @param size 数量
 * @param categoryIds  分类ID数组
 * @param exclude 排除方式
 * @param orderBy 排序方式
 * @return list 藏经列表
 * @return err 异常信息
 */
func (srv *SutraSrv) GetPageSutraByCategory(offset int64, size int64, categoryIds []int, exclude bool, orderBy string) (list []*models.Sutra, err error) {
	var (
		cacheSrv cache.SutraCache
		dataIDs  []string
	)

	ctx := context.Background()
	//不同排序缓存key
	listKey := cacheSrv.GetZSetKey(categoryIds, exclude, orderBy)
	//热度缓存KEY 需要增加key到被缓存key中 更新热度
	if orderBy == "hot" {
		_ = gredis.ZAdd(ctx, cacheSrv.GetCacheHotKey(), listKey, float64(rand.Int()))
	}
	//从缓存中获取分页ID
	if dataIDs, err = gredis.ZRevRange(ctx, listKey, offset, offset+size-1); err != nil {
		logging.Error(err)
		return nil, err
	}

	//补救缓存
	if len(dataIDs) < int(size) {
		modelDao := daos.SutraDao{DB: orm.DB}
		cacheCount, _ := gredis.ZCard(ctx, listKey)                          //缓存中数量
		dbCount, _ := modelDao.GetSutraCountByCategory(categoryIds, exclude) //数据库中数量
		//不统一需要同步
		if cacheCount < dbCount {
			//获取数据库中数据
			dbIds, columns, err := modelDao.GetPageSutraColumnByCategory(0, int(dbCount), categoryIds, exclude, orderBy)
			if err != nil {
				return nil, err
			}

			//空数据
			if dbIds == nil || columns == nil {
				return nil, nil
			}
			//补充到数据库
			for _, column := range columns {
				if orderBy == "hot" {
					_ = gredis.ZAdd(ctx, listKey, column.ID, float64(column.Hot))
				} else {
					createdAt, _ := column.CreatedAt.Unix()
					_ = gredis.ZAdd(ctx, listKey, column.ID, float64(createdAt))
				}
			}

			//获取当前分页数组
			if dbCount > offset {
				var end int64
				diff := dbCount - offset
				if diff > 0 {
					if diff > size {
						end = offset + size
					} else {
						end = offset + diff
					}
					//最大可获得
					dataIDs = dbIds[offset:end]
				}
			}
		}
	}

	//根据缓存ID 获取结构体信息
	for _, cacheID := range dataIDs {
		details := new(models.SutraDetails)

		ID, _ := strconv.ParseUint(cacheID, 10, 64)
		dataKey := cacheSrv.GetDataKey(ID)
		//数据在缓存中，获取数据
		if gredis.Exist(ctx, dataKey) {
			var cacheData string
			cacheData, err = gredis.Get(ctx, dataKey)
			if err == nil {
				err = json.Unmarshal([]byte(cacheData), &details)
			}
		} else {
			//缓存中没找到，刷新缓存获取数据
			details, err = srv.RefreshCacheSutraById(ID)
		}
		//检查err
		if err != nil || details == nil {
			logging.Error(err)
			continue
		}

		//返回结构体
		list = append(list, &models.Sutra{
			ID:           details.ID,
			CategoryID:   details.CategoryID,
			CategoryName: details.CategoryName,
			Name:         details.Name,
			Alias:        details.Alias,
			Volume:       details.Volume,
			Author:       details.Author,
			Source:       details.Source,
			Hot:          details.Hot,
			Copyable:     details.Copyable,
		})
	}

	return list, nil
}

/**
 * @Description: 分页获取藏经章回
 * @receiver srv
 * @param offset 起始
 * @param size 数量
 * @param sutraID 藏经ID
 * @return list 章回列表
 * @return err 异常信息
 */
func (srv *SutraSrv) GetPageChapterBySutra(offset int, size int, sutraID uint64) (list []*models.Chapter, err error) {
	var (
		cacheSrv cache.ChapterCache
		modelDao daos.SutraDao
		dataIDs  []string
	)

	//不同排序缓存key
	listKey := cacheSrv.GetListKey(sutraID)
	//从缓存中获取分页ID
	ctx := context.Background()
	dataIDs, err = gredis.LRange(ctx, listKey, int64(offset), int64(offset+size-1))
	if err != nil {
		logging.Error(err)
		return nil, err
	}

	//缓存中不够 需要从数据库中取
	if len(dataIDs) < size {
		//数据库获取
		dataIDs, _, err = modelDao.GetChaptersBySutra(offset, size, sutraID)
		if err != nil {
			logging.Error(err)
			return nil, err
		}

		//补充数据
		if len(dataIDs) > 0 {
			_ = gredis.LTrim(ctx, listKey, 0, int64(offset))
			_ = gredis.RPush(ctx, listKey, dataIDs)
		}
	}

	//缓存中足够取
	for _, cacheID := range dataIDs {
		details := new(models.ChapterDetails)

		ID, _ := strconv.ParseUint(cacheID, 10, 64)
		dataKey := cacheSrv.GetDataKey(ID)
		//数据在缓存中，获取数据
		if gredis.Exist(ctx, dataKey) {
			var cacheData string
			cacheData, err = gredis.Get(ctx, dataKey)
			if err == nil {
				err = json.Unmarshal([]byte(cacheData), &details)
			}
		} else {
			//缓存中没找到，刷新缓存获取数据
			details, err = srv.RefreshCacheChapterById(ID)
		}
		//检查err
		if err != nil || details == nil {
			logging.Error(err)
			continue
		}

		//返回结构体
		list = append(list, &models.Chapter{
			ID:       details.ID,
			SutraID:  details.SutraID,
			Title:    details.Title,
			Copyable: details.Copyable,
		})
	}

	return list, nil
}

/**
 * @Description: 根据章回ID 获取章回详情
 * @receiver srv
 * @param id 章回ID
 * @return data 章回详情
 * @return err 异常信息
 */
func (srv *SutraSrv) GetChapterById(id uint64) (data *models.ChapterDetails, err error) {

	var cacheSrv cache.ChapterCache
	//查找Redis缓存
	dataKey := cacheSrv.GetDataKey(id)
	ctx := context.Background()
	if gredis.Exist(ctx, dataKey) {
		cacheData, err := gredis.Get(ctx, dataKey)
		if err != nil {
			logging.Info(err)
		} else {
			_ = json.Unmarshal([]byte(cacheData), &data)
			return data, nil
		}
	}

	//刷新缓存，同时获取数据
	if data, err = srv.RefreshCacheChapterById(id); err != nil {
		return nil, err
	}

	return data, nil
}

/**
 * @Description: 刷新藏经缓存
 * @receiver srv 藏经Service
 * @param id 藏经ID
 * @return data 藏经详情
 * @return err 异常信息
 */
func (srv *SutraSrv) RefreshCacheSutraById(id uint64) (data *models.SutraDetails, err error) {

	modelDao := daos.SutraDao{DB: orm.DB}
	//根据ID查找数据库获取文章
	if data, err = modelDao.GetSutra(id); err != nil {
		logging.Error(err)
		return nil, err
	}

	//更新缓存
	var cacheSrv cache.SutraCache
	dataKey := cacheSrv.GetDataKey(id)
	//写一个String缓存, 过期时间1小时
	ctx := context.Background()
	_ = gredis.Set(ctx, dataKey, data, cache.SutraExpireTime)
	//更新缓存列表中 HOT 热度分值
	RefreshCacheKeyHot(cacheSrv.GetCacheHotKey(), data.ID, data.Hot)
	return data, nil
}

/**
 * @Description: 刷新章回缓存
 * @receiver srv
 * @param id 章回ID
 * @return data 章回详情
 * @return err 异常信息
 */
func (srv *SutraSrv) RefreshCacheChapterById(ID uint64) (data *models.ChapterDetails, err error) {

	//根据ID查找数据库获取
	modelDao := daos.SutraDao{DB: orm.DB}
	data, err = modelDao.GetChapter(ID)
	if err != nil {
		logging.Error(err)
		return nil, err
	}

	//空数据
	if data == nil {
		return nil, nil
	}

	var (
		paragraph       []string
		paragraphPinyin []string
	)

	//段落
	paragraph = strings.FieldsFunc(data.Original, func(c rune) bool {
		return unicode.IsControl(c)
	})

	//拼音拆解
	if data.Pinyin != "" && len(data.Pinyin) > 0 {
		paragraphPinyin = strings.FieldsFunc(data.Pinyin, func(c rune) bool {
			return unicode.IsControl(c)
		})
	}

	for i := 0; i < len(paragraph); i++ {
		var (
			paragraphStr       string
			paragraphPinyinStr string
			sentence           []string
			sentencePinyin     []string
			symbol             []string
		)

		paragraphStr = paragraph[i] //段落
		//句子拆解
		sentence = strings.FieldsFunc(paragraphStr, func(c rune) bool {
			return unicode.IsPunct(c)
		})

		//符号拆解
		symbol = strings.FieldsFunc(paragraphStr, func(c rune) bool {
			return !unicode.IsPunct(c)
		})

		//句子拼音
		if paragraphPinyin != nil && len(paragraphPinyin) > i {
			paragraphPinyinStr = paragraphPinyin[i]
			sentencePinyin = strings.FieldsFunc(paragraphPinyinStr, func(c rune) bool {
				return unicode.IsPunct(c)
			})
		}

		var sentenceItems []models.Sentence
		for j := 0; j < len(sentence); j++ {
			var (
				sentenceStr       string
				sentencePinyinStr string
				symbolStr         string
				word              []string
				wordPinyin        []string
			)

			sentenceStr = strings.TrimSpace(sentence[j])
			word = strings.Split(sentenceStr, "")

			//符号
			if symbol != nil && len(symbol) > j {
				symbolStr = symbol[j]
			}

			//句子拼音
			if sentencePinyin != nil && len(sentencePinyin) > j {
				sentencePinyinStr = strings.TrimSpace(sentencePinyin[j])
				wordPinyin = strings.Split(sentencePinyinStr, " ")
			}

			var wordItems []models.Word
			for k := 0; k < len(word); k++ {
				var (
					wordStr       string
					wordPinyinStr string
				)

				wordStr = strings.TrimSpace(word[k])
				if wordPinyin != nil && len(wordPinyin) > k {
					wordPinyinStr = strings.TrimSpace(wordPinyin[k])
				}

				wordItems = append(wordItems, models.Word{
					Index:  k,
					Word:   wordStr,
					Pinyin: wordPinyinStr,
				})
			}

			sentenceItems = append(sentenceItems, models.Sentence{
				Index:  j,
				Text:   sentenceStr,
				Pinyin: sentencePinyinStr,
				Symbol: symbolStr,
				Word:   wordItems,
				Note:   nil,
			})
		}

		data.Paragraph = append(data.Paragraph, models.Paragraph{
			Index:    i,
			Content:  paragraphStr,
			Pinyin:   paragraphPinyinStr,
			Sentence: sentenceItems,
		})
	}

	//更新缓存
	var cacheSrv cache.ChapterCache
	dataKey := cacheSrv.GetDataKey(ID)
	//写一个String缓存, 过期时间1小时
	ctx := context.Background()
	_ = gredis.Set(ctx, dataKey, data, cache.ChapterExpireTime)

	return data, nil
}
